local util = require "util"
local setmetatable = setmetatable
local math = math
local table = table
local type = type
local require = require
local os = os
local io = io
local ngx = ngx
local error = error
local string = string
local tonumber = tonumber
local tostring = tostring
local pairs = pairs
local unpack = unpack

package.cpath = "/usr/nginx/?.so;;"
local json = require 'cjson'
module(...)


_VERSION = '0.1'

_TOKEN_KEY = 'GqAES3Esef1$y$F8OT3WOwzL6MdFjmA8'
_SHA1_KEY = 'tuO4X!7^sSz*JiK&$GZW95tDHg!gmGGJ'
_SIMAGE_KEY = 'Zq9wUt1LVZ&Ea4Is2zX!Rezoh6EPHMm3'
_INDOOR_KEY = '4CSgrYnWOIQu^P6QgIg5AvxZ5UKXREX2'
_KEY2 = 't2Wx6wK4#0Vo%AmdAre3Njh2Rw1E1AHM'
_KEY3 = 'IdG6qIZW^h7KZj*yctl58lhbl1!NUZU!'
_KEY4 = 'xWUtCc^$Gm965L7uZgyuQovCY1KMpjYg'
_KEY5 = 'QAID@uF3$oi258El@6vHaZE8sD5iVSQh'
_MYSQL_HOST = "rm-bp12n6sy24a26a84k.mysql.rds.aliyuncs.com"
_MYSQL_PORT = 3306
_MYSQL_DB = "jieyan"
_MYSQL_USER = "xyitech_rds"
_MYSQL_PASS = "Xunyi2016"

_REDIS_HOSTS = {'127.0.0.1'}
_REDIS_PORTS = {6379}
_REDIS_PASS = {}

incache = false

function clear()
    if ngx.ctx.redis then
        ngx.ctx.redis:set_keepalive(120000, 1024)
    end
    if ngx.ctx.db then
        ngx.ctx.db:set_keepalive(120000, 1024)
    end
    if ngx.ctx.mysql then
        ngx.ctx.mysql:set_keepalive(120000, 1024)
    end
end

function get_ssdb(renew)
    if ngx.ctx.db then
        if renew then
            ngx.ctx.db = nil
        else
            return ngx.ctx.db
        end
    end
    local ssdb = require "ssdb"
    local db = ssdb:new()
    if not db then
        ngx.log(ngx.ERR, "failed to new ssdb")
        ngx.exit(500)
    end
    db:set_timeout(5000)
    local ok, err = db:connect(_SSDB_HOST, _SSDB_PORT)
    if not ok then
        for i = 1,3,1 do
            ok, err = db:connect(_SSDB_HOST, _SSDB_PORT)
            if ok then
                break
            end
        end
        if not ok then
            ngx.log(ngx.ERR, "failed to connect: " .. err)
            ngx.exit(500)
        end
    end
    db:query("set names utf8")
    ngx.ctx.db = db
    return db
end

function get_redis(renew)
    renew = renew or 1
    if #_REDIS_HOSTS < renew then
        ngx.log(ngx.ERR, "no redis host")
        ngx.exit(500)
    end
    if ngx.ctx.redis then
        if renew > 1 then
            ngx.ctx.redis:close()
            ngx.ctx.redis = nil
        else
            return ngx.ctx.redis
        end
    end
    local redis = require "redis"
    local red = redis:new()
    if not red then
        ngx.log(ngx.ERR, "failed to new redis")
        ngx.exit(500)
    end
    red:set_timeout(5000)
    local ok, err = red:connect(_REDIS_HOSTS[renew], _REDIS_PORTS[renew])
    if not ok then
        for i = 1,3,1 do
            ok, err = red:connect(_REDIS_HOSTS[renew], _REDIS_PORTS[renew])
            if ok then
                break
            end
        end
        if not ok then
            ngx.log(ngx.ERR, "failed to connect: " .. err)
            ngx.exit(500)
        end
    end
    if _REDIS_PASS and _REDIS_PASS[renew] then
        local res, err = red:auth(_REDIS_PASS[renew])
        if not res then
            ngx.log(ngx.ERR, "failed to authenticate: ", err)
            ngx.exit(500)
        end
    end
    ngx.ctx.redis = red
    return red
end

function get_mysql(renew)
    if ngx.ctx.mysql then
        if renew then
            ngx.ctx.mysql = nil
        else
            return ngx.ctx.mysql
        end
    end
    local mysql = require "mysql"
    local db, err = mysql:new()
    if not db then
        ngx.log(ngx.ERR, "failed to instantiate mysql: " .. err)
        ngx.exit(500)
    end
    db:set_timeout(2000) -- 1 sec

    local ok, err, errno = db:connect{
        host = _MYSQL_HOST,
        port = _MYSQL_PORT,
        database = _MYSQL_DB,
        user = _MYSQL_USER,
        password = _MYSQL_PASS,
        max_packet_size = 1024 * 1024 }
    if not ok then
        ngx.log(ngx.ERR, "failed to connect: " .. err or 'no error' .. " : " .. errno or 'no errorno')
        return
    end
    db:query("set names utf8")
    ngx.ctx.mysql = db
    return db
end

function get_token(id, aid)
    local now = os.time() .. '@' .. ngx.req.get_headers()["Host"]
    local red = get_redis()
    local ok = red:set("user"..id, aid)
    if not ok then
        ngx.print(json.encode({
            err = -1,
            msg = '设置token失败'
        }))
        ngx.exit(200)
    end
    return ngx.encode_base64(id .. '#' .. now .. '#' .. ngx.encode_base64(ngx.hmac_sha1(_SHA1_KEY, id .. '-' .. now .. '-' .. _TOKEN_KEY)))
end


function userDetail(args)
    local db = get_mysql()
    local user = db:query("SELECT `user`.id, `user`.name,`user`.spoint_id,`user`.org_id,`user`.aid,`user`.`create_time`, `user`.username,`user`.phone,`user`.login_time,`user`.ip_details,`role`.name as roleName, `user`.role_id, `role`.`permissions` FROM `user` left join `role` on `user`.role_id = `role`.id WHERE `user`.id = " .. args.id)
    -- ngx.log(ngx.ERR, json.encode(user))
    if (not user) or (not user[1]) then
        return nil
    end
    user = user[1]
    user.aids = get_aids(user.aid)
    user.orgIds = get_orgIds(user.org_id)
    return user
end

function parse_token(args, controller)
    local token = args.token
    if not token then
        ngx.exit(400)
    end
    token = ngx.decode_base64(token)
    if not token then
        ngx.print(json.encode({
            err = -1,
            msg = 'token不合法或过期'
        }))
        ngx.exit(200)
    end
    local split = util.split
    local items = split(token, '#')
    if #items ~= 3 then
        ngx.print(json.encode({
            err = -2,
            msg = 'token不合法或过期'
        }))
        ngx.exit(200)
    end
    
    local t, host = unpack(split(items[2], '@'))
    t = tonumber(t)
    if host ~= ngx.req.get_headers()["Host"] or not t then
        ngx.print(json.encode({
            err = -3,
            msg = 'token不合法或过期'
        }))
        ngx.exit(200)
    end
    if items[3] ~= ngx.encode_base64(ngx.hmac_sha1(_SHA1_KEY, items[1] .. '-' .. items[2] .. '-' .. _TOKEN_KEY)) then
        ngx.print(json.encode({
            err = -4,
            msg = 'token不合法或过期'
        }))
        ngx.exit(200)
    else
        local red = get_redis()
        local res = red:get("user"..items[1])
        if res == ngx.null then
            ngx.print(json.encode({
                err = -5,
                msg = 'token不合法或过期'
            }))
            ngx.exit(200)
        end
        local aid = tonumber(res)
        if not aid then
            ngx.print(json.encode({
                err = -6,
                msg = 'token无法找到匹配的区域'
            }))
            ngx.exit(200)
        end
        
        local aids = get_aids(aid)
        local db = get_mysql()
        -- local userModule = require 'user'
        -- local user = db:query("SELECT `user`.id, `user`.name,`user`.spoint_id,`user`.org_id,`user`.aid,`user`.`create_time`, `user`.username,`user`.phone,`user`.login_time,`user`.ip_details,`role`.name as roleName, `user`.role_id, `role`.`permissions` FROM `user` left join `role` on `user`.role_id = `role`.id WHERE `user`.id = " .. items[1])
        -- ngx.log(ngx.ERR, type(userModule.detail))
        local user = userDetail({["id"]= items[1]})
        if (not user) then
            ngx.print(json.encode({
                err = 1,
                msg = '用户信息有误'
            }))
            ngx.exit(200) 
        end
        -- ngx.log(ngx.ERR, json.encode(user))
        check_permissions(controller, user, args)
        return user
    end
end

function get_aids(aid)
    local details
    local db = get_mysql()
    local areas = db:query("SELECT * FROM `area`")
    if not areas then
        ngx.print(json.encode({
            err = 1,
            msg = '数据库出错'
        }))
        ngx.exit(200)
    end
        
    details = _arrangeAids(areas)
    return _getAids(details)[tonumber(aid)]
end

function get_orgIds(aid)
    local details
    local db = get_mysql()
    local orgs = db:query("SELECT * FROM `organization`")
    if not orgs then
        ngx.print(json.encode({
            err = 1,
            msg = '数据库出错'
        }))
        ngx.exit(200)
    end
        
    details = _arrangeAids(orgs)
    return _getAids(details)[tonumber(aid)]
end

function _arrangeAids(areas)
    local res = {}
    -- local json = require("cjson")
    for k,area in pairs(areas) do
        local id = tonumber(area["id"])
        local pid = tonumber(area["pid"])
        if not res[id] then
            res[id] = {}
        end

        -- if  pid ~= "0" then
            if not res[pid] then
                res[pid] = {id}
            else
                res[pid][#(res[pid]) + 1] = id
            end
        -- end
    end

    
    return res
end

function _getAids(areas)
    local res = {}
    for id,childrenIds in pairs(areas) do
        _getAid(id, childrenIds, areas, res)
    end
    return res
end

function _getAid(id, childrenIds, areas, res)
    if res[id] then return res[id] end

    if #childrenIds == 0 then
        res[id] = {id}
    else
        for i, v in pairs(childrenIds) do
            if not res[id] then res[id] = {id} end
            local tmp = res[v]
            if not res[v] then
                tmp = _getAid(v, areas[v], areas, res)
            end
            res[id][#(res[id]) + 1] = v
            res[id] = require("util").append(res[id], tmp)
        end
    end
    return res[id]
end

function check_permissions(controller, user, args)
    if not controller then return true end

    local permissions = util.split(user["permissions"], ',')
    for i, perm in pairs(permissions) do
        if perm == controller or perm == "all" then
            return true
        end
    end

    if check_area(user, args) and check_org(user, args) then
        return true
    end
    
    ngx.print(json.encode({
            err = 2,
            msg = '权限不足'
        }))
    ngx.exit(200) 
end

function check_area(user, args)
   if args.aid then
        if util.contains(user.aids, tonumber(args.aid)) then
            return true
        else 
            ngx.print(json.encode({
                err = 2,
                msg = '创建失败, 权限不足'
            }))
            ngx.exit(200)
        end
    end
    return true
end

function getFidInfo(fid)
    if not ngx.ctx.fidInfo then
        ngx.ctx.fidInfo = {}
    end
    if fid then 
        if not ngx.ctx.fidInfo[fid..""] then
            ngx.ctx.fidInfo[fid..""] = {}
        end
        return ngx.ctx.fidInfo[fid..""]
    end
    return ngx.ctx.fidInfo
end

function check_org(user, args)
    if args.org_id then
        if util.contains(user.orgIds, tonumber(args.org_id)) then
            return true
        else 
            ngx.print(json.encode({
                err = 2,
                msg = '创建失败, 权限不足'
            }))
            ngx.exit(200)
        end
    end
    return true
end

local mt = {
    __newindex = function (table, key, val)
        error('attempt to write to undeclared variable "' .. key .. '"')
    end
}

setmetatable(_M, mt)

